#include <std.h>
#include <pio.h>
#include <system.h>
#include <error.h>
#include <type.h>
#include <dint.h>

/* SuperVGA information block */
struct
{
    char    VESASignature[4];       /* 'VESA' 4 byte signature          */
    short   VESAVersion;            /* VBE version number               */
    char    *OEMStringPtr;      /* Pointer to OEM string            */
    long    Capabilities;           /* Capabilities of video card       */
    unsigned *VideoModePtr;     /* Pointer to supported modes       */
    short   TotalMemory;            /* Number of 64kb memory blocks     */
    char    reserved[236];          /* Pad to 256 byte block size       */
}__attribute__((packed)) *VbeInfoBlock;
/* SuperVGA mode information block */
struct
{
    u16 ModeAttributes;      /* Mode attributes                 */
    u8  WinAAttributes;      /* Window A attributes             */
    u8  WinBAttributes;      /* Window B attributes             */
    u16 WinGranularity;      /* Window granularity in k         */
    u16 WinSize;             /* Window size in k                */
    u16 WinASegment;         /* Window A segment                */
    u16 WinBSegment;         /* Window B segment                */
    void (*WinFuncPtr)(void);       /* Pointer to window function      */
    u16 BytesPerScanLine;    /* Bytes per scanline              */
    u16 XResolution;         /* Horizontal resolution           */
    u16 YResolution;         /* Vertical resolution             */
    u8  XCharSize;           /* Character cell width            */
    u8  YCharSize;           /* Character cell height           */
    u8  NumberOfPlanes;      /* Number of memory planes         */
    u8  BitsPerPixel;        /* Bits per pixel                  */
    u8  NumberOfBanks;       /* Number of CGA style banks       */
    u8  MemoryModel;         /* Memory model type               */
    u8  BankSize;            /* Size of CGA style banks         */
    u8  NumberOfImagePages;  /* Number of images pages          */
    u8  res1;                /* Reserved                        */
    u8  RedMaskSize;         /* Size of direct color red mask   */
    u8  RedFieldPosition;    /* Bit posn of lsb of red mask     */
    u8  GreenMaskSize;       /* Size of direct color green mask */
    u8  GreenFieldPosition;  /* Bit posn of lsb of green mask   */
    u8  BlueMaskSize;        /* Size of direct color blue mask  */
    u8  BlueFieldPosition;   /* Bit posn of lsb of blue mask    */
    u8  RsvdMaskSize;        /* Size of direct color res mask   */
    u8  RsvdFieldPosition;   /* Bit posn of lsb of res mask     */
    u8  DirectColorModeInfo; /* Direct color mode attributes    */
    u32 phys_base;
    u32 offscreen_start;
    u16 offscreen_memory;
    u16 bytesperscanlin;
    u8 n_image_banked;
    u8 n_image_linear;
    u8 lin_red_mask;
    u8 lin_red_mask_shift;
    u8 lin_green_mask;
    u8 lin_green_mask_shift;
    u8 lin_blue_mask;
    u8 lin_blue_mask_shift;
    u8 lin_res_mask;
    u8 lin_res_mask_shift;
    u32 max_pixel_clock;
    u8 reserved[190];    
} __attribute__((packed))*ModeInfoBlock;

static u8 *phys_base;

int getVbeInfo()
{
	VbeInfoBlock=(void *)0x700;
	bioscall(0x4f00,0,0,0,0,0x0700,0x10);
	return (ax==0x4f);
}

int getModeInfo(int mode)
{
    union REGS in,out;
    ModeInfoBlock=(void *)0x800;
    if (mode < 0x100) return 0;     /* Ignore non-VBE modes             */
    in.x.ax=0x4f01;
    in.x.cx=mode;
    in.x.di=0x0800;
   	int86(0x10,&in,&out);
    if (out.x.ax != 0x4F) return 0;
    return 1;
}

/* Set a VBE video mode */
void setVBEMode(int mode)
{
    union REGS in,out;
    in.x.ax = 0x4F02; in.x.bx = mode;
    int86(0x10,&in,&out);
}
/* Return the current VBE video mode */
int getVBEMode(void)
{
    union REGS in,out;
    in.x.ax = 0x4F03;
    int86(0x10,&in,&out);
    return out.x.bx;
}

/* Set new read/write bank. Set both Window A and Window B, as many VBE's have
 * these set as separately available read and write windows. We also use a 
 * simple (but very effective) optimization of checking if the requested bank 
 * is currently active. */
static int curBank;
static unsigned int bankShift;
void setBank(int bank)
{
    union REGS  in,out;
    if (bank == curBank) return;    /* Bank is already active           */
    curBank = bank;                 /* Save current bank number         */
    bank <<= bankShift;             /* Adjust to window granularity     */
    in.x.ax = 0x4F05; in.x.bx = 0;  in.x.dx = bank;
    int86(0x10, &in, &out);
    in.x.ax = 0x4F05; in.x.bx = 1;  in.x.dx = bank;
    int86(0x10, &in, &out);
}

void putPixel(int x,int y,u32 color)
{
    long addr = (long)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
    setBank((int)(addr >> 16));
    switch(ModeInfoBlock->BitsPerPixel)	{
    	case 32:
    	*(phys_base + (addr & 0xFFFF)) = (char)(color);
    	*(phys_base + (addr & 0xFFFF)+1) = (char)(color>>8);
    	*(phys_base + (addr & 0xFFFF)+2) = (char)(color>>16);
    	*(phys_base + (addr & 0xFFFF)+3) = (char)(color>>24);
    	break;
    	case 8:
    	*(phys_base + (addr & 0xFFFF)) = (char)(color);
    	break;
    } 
}

/* Draw a line from (x1,y1) to (x2,y2) in specified color */
void line(int x1,int y1,int x2,int y2,u32 color)
{
    int     d;                      /* Decision variable                */
    int     dx,dy;                  /* Dx and Dy values for the line    */
    int     Eincr,NEincr;           /* Decision variable increments     */
    int     yincr;                  /* Increment for y values           */
    int     t;                      /* Counters etc.                    */
#define ABS(a)   ((a) >= 0 ? (a) : -(a))
    dx = ABS(x2 - x1);
    dy = ABS(y2 - y1);
    if (dy <= dx)
    {
        /* We have a line with a slope between -1 and 1. Ensure that we are 
         * always scan converting the line from left to right to ensure that 
         * we produce the same line from P1 to P0 as the line from P0 to P1. */
        if (x2 < x1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
        }
        if (y2 > y1)
            yincr = 1;
        else
            yincr = -1;
        d = 2*dy - dx;              /* Initial decision variable value  */
        Eincr = 2*dy;               /* Increment to move to E pixel     */
        NEincr = 2*(dy - dx);       /* Increment to move to NE pixel    */
        putPixel(x1,y1,color);      /* Draw the first point at (x1,y1)  */
        /* Incrementally determine the positions of the remaining pixels */
        for (x1++; x1 <= x2; x1++)

        {
            if (d < 0)
                d += Eincr;         /* Choose the Eastern Pixel         */
            else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                y1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
            }
            putPixel(x1,y1,color);  /* Draw the point                   */
        }
    }
    else
    {
        /* We have a line with a slope between -1 and 1 (ie: includes vertical
         * lines). We must swap x and y coordinates for this. Ensure that we
         * are always scan converting the line from left to right to ensure 
         * that we produce the same line from P1 to P0 as line from P0 to P1.*/
        if (y2 < y1)
        {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
        }
        if (x2 > x1)
            yincr = 1;
        else
            yincr = -1;
        d = 2*dx - dy;              /* Initial decision variable value  */
        Eincr = 2*dx;               /* Increment to move to E pixel     */
        NEincr = 2*(dx - dy);       /* Increment to move to NE pixel    */
        putPixel(x1,y1,color);      /* Draw the first point at (x1,y1)  */
        /* Incrementally determine the positions of the remaining pixels */
        for (y1++; y1 <= y2; y1++)
        {
            if (d < 0)
                d += Eincr;         /* Choose the Eastern Pixel         */
            else
            {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                x1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
            }
            putPixel(x1,y1,color);  /* Draw the point                   */
        }
    }
}

void box(int x,int y,int x1,int y1,u32 color)
{
	line(x,y,x1,y,color);
	line(x,y,x,y1,color);
	line(x1,y,x1,y1,color);
	line(x,y1,x1,y1,color);
}

/* Draw a simple moire pattern of lines on the display */
void drawMoire(void)
{
    int     i;
    for (i = 0; i < ModeInfoBlock->XResolution; i += 5)
    {
        line(ModeInfoBlock->XResolution/2,ModeInfoBlock->YResolution/2,i,0,i % 0xFF);
        line(ModeInfoBlock->XResolution/2,ModeInfoBlock->YResolution/2,i,ModeInfoBlock->YResolution,(i+1) % 0xFF);
    }
    for (i = 0; i < ModeInfoBlock->YResolution; i += 5)
    {
        line(ModeInfoBlock->XResolution/2,ModeInfoBlock->YResolution/2,0,i,(i+2) % 0xFF);
        line(ModeInfoBlock->XResolution/2,ModeInfoBlock->YResolution/2,ModeInfoBlock->XResolution,i,(i+3) % 0xFF);
    }
    line(0,0,ModeInfoBlock->XResolution-1,0,15);
    line(0,0,0,ModeInfoBlock->YResolution-1,15);
    line(ModeInfoBlock->XResolution-1,0,ModeInfoBlock->XResolution-1,ModeInfoBlock->YResolution-1,15);
    line(0,ModeInfoBlock->YResolution-1,ModeInfoBlock->XResolution-1,ModeInfoBlock->YResolution-1,15);
}

void *flat(void *ptr)
{
	u16 seg,off;
	void *flat;
	off=(u16)(u32)(ptr);
    seg=(u16)((u32)(ptr)>>16);
    flat=(void *)(seg*0x10+off);
    return flat;
}

/* Display a list of available resolutions. Be careful with calls to function
 * 00h to get SuperVGA mode information. Many VBE's build the list of video
 * modes directly in this information block, so if you are using a common 
 * buffer (which we aren't here, but in protected mode you will), then you will
 * need to make a local copy of this list of available modes. */

void availableModes(void)
{
    unsigned short *p;
    int i,s=0;
    if (!getVbeInfo())
    {
        printdev("\nNo VESA VBE detected");
       	return;
    }
    printdev("\nVESA VBE Version %d.%d detected (%s)",VbeInfoBlock->VESAVersion >> 8, VbeInfoBlock->VESAVersion & 0xF,flat(VbeInfoBlock->OEMStringPtr));
	for(p=flat(VbeInfoBlock->VideoModePtr);;p++)	{
		if(*p==0xffff)	{
			break;
		}
		if(getModeInfo(*p))	{
			if(ModeInfoBlock->ModeAttributes&0x81==0x81)	{
				printdev("\nMode:%x %d x %d %d bpp",*p,ModeInfoBlock->XResolution,ModeInfoBlock->YResolution,ModeInfoBlock->BitsPerPixel);
			}
		}
	}
}

u32 init_mode(u32 mode)
{
    int i;
    if (!getVbeInfo())
    {
        printdev("No VESA VBE detected\n");
        return;
    }
    i=getModeInfo(mode);
    if(i && ModeInfoBlock->ModeAttributes & 0x81==0x81)	{
		phys_base=(u8 *)0xa0000;
    	bankShift = 0;
        while ((unsigned)(64 >> bankShift) != ModeInfoBlock->WinGranularity)	{
        	bankShift++;
    	}
    	setVBEMode(mode);
    	return SUCCESS;
    }
    return FAILED;
}

u32 col(u8 red,u8 green,u8 blue)
{
	return 0xff000000+red*0x10000+green*0x100+blue;
}

int init_vbe()
{
	availableModes();
	return NULL;
}

kdrv vbe={
	.name="vesabios",
	.init=init_vbe,
	.reset=NULL,
	.deinit=NULL,
	.select_subdevice=init_mode,		//select_subdevice will be the mode
	.read_char=NULL,
	.read_block=NULL,
	.write_char=NULL,
	.write_block=NULL,
};

